home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AmigActive 10
/
AACD 10.iso
/
AACD
/
Online
/
SpeakFreely
/
src
/
mike.c
< prev
next >
Wrap
C/C++ Source or Header
|
2000-05-18
|
67KB
|
2,333 lines
#define USE_CURSES
#define BYTE_SWAP_DEBUG
/*
Speak Freely for Unix
Network sound transmission program
Designed and implemented in July of 1991 by John Walker
*/
#include "speakfree.h"
#include "version.h"
/* Destination host descriptor. */
struct destination {
struct destination *dnext; /* Next destination in list */
char *server; /* Host name identifier string */
struct sockaddr_in name; /* Internet address */
struct sockaddr_in ctrl; /* RTCP port address */
unsigned char time_to_live; /* Multicast scope specification */
char deskey[9]; /* Destination DES key, if any */
char rtpdeskey[9]; /* Destination RTP DES key, if any */
char vatdeskey[9]; /* Destination VAT DES key, if any */
char ideakey[17]; /* Destination IDEA key, if any */
char pgpkey[17]; /* Destination PGP key, if any */
char blowfish_spec; /* Nonzero if Blowfish key specified */
#ifdef CRYPTO
BF_KEY blowfishkey; /* Destination Blowfish key, if any */
#endif
char *otp; /* Key file */
};
static char *progname; /* Program name */
static int sock; /* Communication socket */
static struct destination *dests = NULL, *dtail;
static int compressing = FALSE; /* Compress sound: simple 2X */
static int gsmcompress = TRUE; /* GSM compress buffers */
#ifdef OBSOLETE
static int gsm_byte_order_kludge = TRUE; /* Transition to correct byte order for GSM ? */
#endif
#ifdef BYTE_SWAP_DEBUG
static int gsm_byte_order_debug = FALSE; /* Send GSM length field with swapped bytes */
#endif
static int lpccompress = FALSE; /* LPC compress buffers */
static int lpc10compress = FALSE; /* LPC-10 compress buffers */
static int adpcmcompress = FALSE; /* ADPCM compress buffers */
static int toasted = FALSE; /* Sending already-compressed file ? */
static int robust = 1; /* Robust duplicate packets mode */
static int rseq = 0; /* Robust mode packet sequence number */
#define DEFAULT_SQUELCH 4096 /* Default squelch level */
static int squelch = 0; /* Squelch level if > 0 */
static int sqdelay = 12000; /* Samples to delay before squelch */
static int sqwait = 0; /* Squelch delay countdown */
#ifdef PUSH_TO_TALK
static int push = TRUE; /* Push to talk mode */
static int talking = FALSE; /* Push to talk button state */
static int rawmode = FALSE; /* Is terminal in raw mode ? */
#endif
static int ring = FALSE; /* Force speaker & level on next pkt ? */
static int rtp = FALSE; /* Use Internet Real-Time Protocol */
static int vat = FALSE; /* Use VAT protocol */
static int agc = FALSE; /* Automatic gain control active ? */
static int rgain = 33; /* Current recording gain level */
static int spurt = TRUE; /* True for first packet of talk spurt */
static int debugging = FALSE; /* Debugging enabled here and there ? */
static FILE *audioDumpFile = NULL; /* Audio dump file, if any */
static int havesound = FALSE; /* True if we've acquired sound input */
static char hostname[20]; /* Host name to send with packets */
static int loopback = FALSE; /* Remote loopback mode */
static gsm gsmh; /* GSM handle */
static struct adpcm_state adpcm = {0, 0}; /* ADPCM compression state */
static char *vatid = NULL; /* VAT ID packet */
static int vatidl; /* VAT ID packet length */
static unsigned long ssrc; /* RTP synchronisation source identifier */
static unsigned long timestamp; /* RTP packet timestamp */
static unsigned short seq; /* RTP packet sequence number */
static unsigned long rtpdesrand; /* RTP DES random RTCP prefix */
static char *sdes = NULL; /* RTP SDES packet */
static int sdesl; /* RTP SDES packet length */
static char curkey[9] = ""; /* Current DES key if curkey[0] != 0 */
static char currtpkey[9] = ""; /* Current RTP DES key if currtpkey[0] != 0 */
static char curvatkey[9] = ""; /* Current VAT DES key if currtpkey[0] != 0 */
static char curideakey[17] = ""; /* Current IDEA key if curideakey[0] != 0 */
static char curpgpkey[17] = ""; /* Current PGP key if curpgpkey[0] != 0 */
static char curblowfish_spec = FALSE; /* Nonzero if Blowfish key specified */
#ifdef CRYPTO
static BF_KEY curblowfishkey; /* Blowfish key */
#endif
static char *curotp = NULL; /* Key file buffer */
static int sound_packet; /* Ideal samples/packet */
static struct soundbuf *pgpsb = NULL; /* PGP key sound buffer, if any */
static LONG pgpsbl; /* Length of PGP key sound buffer data */
#ifdef HALF_DUPLEX
static struct in_addr localhost; /* Our internet address */
static int halfDuplexMuted = FALSE; /* Muted by half-duplex transmission */
#endif
static int hasFace = FALSE; /* Is a face image available ? */
#ifdef sgi
static long usticks; /* Microseconds per clock tick */
#endif
#define TimerStep (7 * 1000000L) /* Alarm interval in microseconds */
#ifdef HEWLETT_PACKARD
extern int HPTermHookChar;
#endif
/* Audio input and control device file names. */
#ifdef AUDIO_DEVICE_FILE
extern char *devAudioInput, *devAudioControl;
#endif
#define ucase(x) (islower(x) ? toupper(x) : (x))
#ifndef USE_CURSES
#ifndef UNIX420
#define UNIX5
#endif
#ifdef UNIX420
#include <sgtty.h>
#endif /* UNIX420 */
#ifdef UNIX5
#include <termio.h>
static struct termio old_term_params;
#endif /* UNIX5 */
/* Set raw mode on terminal file. Basically, get the terminal into a
mode in which all characters can be read as they are entered. CBREAK
mode is not sufficient. */
static void tty_rawmode()
{
#ifdef UNIX420
struct sgttyb arg;
ioctl(fileno(stdin), TIOCGETP, &arg); /* get basic parameters */
arg.sg_flags |= RAW; /* set raw mode */
arg.sg_flags &= ~ECHO; /* clear echo mode */
ioctl(fileno(stdin), TIOCSETP, &arg); /* set it */
#endif /* UNIX420 */
#ifdef UNIX5
struct termio term_params;
ioctl(fileno(stdin), TCGETA, &old_term_params);
term_params = old_term_params;
term_params.c_iflag &= ~(ICRNL|IXON|IXOFF); /* no cr translation */
term_params.c_iflag &= ~(ISTRIP); /* no stripping of high order bit */
term_params.c_oflag &= ~(OPOST); /* no output processing */
term_params.c_lflag &= ~(ISIG|ICANON|ECHO); /* raw mode */
term_params.c_cc[4] = 1; /* satisfy read after 1 char */
ioctl(fileno(stdin), TCSETAF, &term_params);
#endif /* UNIX5 */
/*printf("\n(raw)\n");*/
}
/* Restore tty mode */
static void tty_normode()
{
#ifdef UNIX420
struct sgttyb arg;
ioctl(fileno(stdin), TIOCGETP, &arg); /* get basic parameters */
arg.sg_flags &= ~RAW; /* clear raw mode */
arg.sg_flags |= ECHO; /* set echo mode */
ioctl(fileno(stdin), TIOCSETP, &arg); /* set it */
#endif /* UNIX420 */
#ifdef UNIX5
ioctl(fileno(stdin), TCSETAF, &old_term_params);
#endif /* UNIX5 */
/*printf("\n(cooked)\n");*/
}
#endif
/* ULARM -- Wrapper for setitimer() that looks like alarm()
but accepts a time in microseconds. */
static void ularm(t)
long t;
{
struct itimerval it;
it.it_value.tv_sec = t / 1000000L;
it.it_value.tv_usec = t % 1000000L;
it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
}
/* GSMCOMP -- Compress the contents of a sound buffer using GSM. */
static void gsmcomp(sb)
struct soundbuf *sb;
{
gsm_signal src[160];
gsm_frame dst;
int i, j, l = 0;
char *dp = ((char *) sb->buffer.buffer_val) + sizeof(short);
sb->compression |= fCompGSM;
for (i = 0; i < sb->buffer.buffer_len; i += 160) {
for (j = 0; j < 160; j++) {
if ((i + j) < sb->buffer.buffer_len) {
src[j] = audio_u2s(sb->buffer.buffer_val[i + j]);
} else {
src[j] = 0;
}
}
gsm_encode(gsmh, src, dst);
bcopy(dst, dp, sizeof dst);
dp += sizeof dst;
l += sizeof dst;
}
/* Hide original uncompressed buffer length in first 2 bytes of buffer. */
#ifdef OBSOLETE
/* Allow me to explain the following if statement. Speak
Freely for Unix prior to release 6.1e contained a bug
which caused it to both send (here) and receive
(in speaker.c) GSM packets with the embedded length
in host byte order. This went undetected for a long
time because all the Unix machines I have access to
for testing are big-endian and thus interoperated
correctly with one another and the Windows version
(which does handle the byte order correctly). Many
Linux and FreeBSD users only speak to one another,
and thus never noticed the problem. It was only when
a little-endian Unix machine attempted to use GSM
compression in a connection with a Windows or
big-endian Unix machine that what came to be called
the "deafening static" problem manifested itself.
The fix is obvious, but we have to get there in
a cautious manner to avoid breaking interoperability
during the transition. So here's the plan: starting
with Unix release 6.1e and the corresponding Windows
release, the GSM receiving code in both versions
will accept the GSM packet length field in
*either* byte order (a byte-swapped value in this
field can be detected unambiguously because if
byte-swapped it will exceed the actual length of
the packet, which is otherwise impossible). Users
who install these releases will be able to receive
GSM sound from any version of Speak Freely on any
platform, present or future. Version 6.1e for
Unix will, however, continue to send the GSM length
field in host byte order. Why? Because otherwise
Linux and FreeBSD users who communicate with one
another would both have to install 6.1e before
they could communicate once again. To make the
transition smoother, 6.1e offers a "-tc" option,
which causes the GSM length field to be sent
in the correct byte order. This option should be
set whenever a Unix user wishes to communicate
with a Windows user in GSM mode. It will also
fix connections to big-endian Unix versions
prior to 6.1e. Once a majority of the installed
base is on the versions which don't care about
byte order in this field, sending in network byte
order will be the default. */
if (gsm_byte_order_kludge) {
#endif
*((short *) sb->buffer.buffer_val) = htons((short) sb->buffer.buffer_len);
#ifdef OBSOLETE
} else {
*((short *) sb->buffer.buffer_val) = sb->buffer.buffer_len;
}
#endif
#ifdef BYTE_SWAP_DEBUG
if (gsm_byte_order_debug) {
unsigned char *bsp = (unsigned char *) sb->buffer.buffer_val;
bsp[0] = sb->buffer.buffer_len & 0xFF;
bsp[1] = (sb->buffer.buffer_len >> 8) & 0xFF;
}
#endif
sb->buffer.buffer_len = l + sizeof(short);
}
/* ADPCMCOMP -- Compress the contents of a sound buffer using ADPCM. */
static void adpcmcomp(sb)
struct soundbuf *sb;
{
unsigned char *dp = (unsigned char *) sb->buffer.buffer_val;
struct adpcm_state istate;
istate = adpcm;
sb->compression |= fCompADPCM;
adpcm_coder_u(dp, (char *) dp, sb->buffer.buffer_len, &adpcm);
sb->buffer.buffer_len /= 2;
/* Hide the ADPCM encoder state at the end of this buffer.
The shifting and anding makes the code byte-order
insensitive. */
dp += sb->buffer.buffer_len;
*dp++ = ((unsigned int) istate.valprev) >> 8;
*dp++ = istate.valprev & 0xFF;
*dp = istate.index;
sb->buffer.buffer_len += 3;
}
/* LPCCOMP -- Compress the contents of a sound buffer using LPC. */
static void lpccomp(sb)
struct soundbuf *sb;
{
int i, l = 0;
char *dp = ((char *) sb->buffer.buffer_val) + sizeof(short);
unsigned char *src = ((unsigned char *) sb->buffer.buffer_val);
lpcparams_t lp;
sb->compression |= fCompLPC;
for (i = 0; i < sb->buffer.buffer_len; i += LPC_FRAME_SIZE) {
lpc_analyze(src + i, &lp);
bcopy(&lp, dp, LPCRECSIZE);
dp += LPCRECSIZE;
l += LPCRECSIZE;
}
/* Hide original uncompressed buffer length in first 2 bytes of buffer. */
*((short *) sb->buffer.buffer_val) = htons((short) sb->buffer.buffer_len);
sb->buffer.buffer_len = l + sizeof(short);
}
/* LPC10COMP -- Compress the contents of a sound buffer using LPC-10. */
static void lpc10comp(sb)
struct soundbuf *sb;
{
char *dp = (char *) sb->buffer.buffer_val;
unsigned char *src = ((unsigned char *) sb->buffer.buffer_val);
sb->compression |= fCompLPC10;
sb->buffer.buffer_len = lpc10encode(src, dp, sb->buffer.buffer_len);
}
/* LPC10STUFF -- Stuff last 16 bytes of LPC10 packet in sendinghost. */
static int lpc10stuff(sb, pktlen)
struct soundbuf *sb;
int pktlen;
{
if ((sb->compression & fCompLPC10) && (sb->buffer.buffer_len > 16)) {
bcopy(((char *) sb) + (pktlen - (sizeof sb->sendinghost)),
sb->sendinghost, sizeof sb->sendinghost);
pktlen -= sizeof sb->sendinghost;
}
return pktlen;
}
/* SENDRTPCTRL -- Send RTP or VAT control message, encrypting if
necessary. */
static int sendrtpctrl(d, destaddr, msg, msgl, rtppkt)
struct destination *d;
struct sockaddr *destaddr;
char *msg;
int msgl, rtppkt;
{
char aux[1024];
#ifdef CRYPTO
if ((rtp || vat) && d->rtpdeskey[0]) {
int vlen;
des_cblock key;
des_key_schedule sched;
des_cblock ivec;
bzero(ivec, 8);
if (rtppkt) {
/* Encrypted RTCP messages are prefixed with 4 random
bytes to prevent known plaintext attacks. */
bcopy(&rtpdesrand, aux, 4);
bcopy(msg, aux + 4, msgl);
msgl += 4;
} else {
bcopy(msg, aux, msgl);
}
/* If we're DES encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire DES frame is sent. This applies only to
VAT, as the code that creates RTCP packets guarantees
they're already padded to a multiple of 8 bytes. */
vlen = msgl;
msgl = (msgl + 7) & (~7);
if (msgl > vlen) {
bzero(aux + vlen, msgl - vlen);
}
if (debugging) {
fprintf(stderr, "Encrypting %d VAT/RTP bytes with DES key.\r\n",
msgl);
}
des_set_key((des_cblock *) ((rtppkt ? d->rtpdeskey : d->vatdeskey) + 1), sched);
des_ncbc_encrypt((des_cblock *) aux,
(des_cblock *) aux, msgl, sched,
(des_cblock *) ivec, DES_ENCRYPT);
msg = aux;
}
#endif
return sendto(sock, msg, msgl, 0, destaddr, sizeof d->ctrl);
}
/* ADDEST -- Add destination host to host list. */
static int addest(host)
char *host;
{
struct destination *d;
struct hostent *h;
long naddr;
unsigned int ttl = 1;
char *mcs;
int curport = Internet_Port;
/* If a multicast scope descriptor appears in the name, scan
it and lop it off the end. We'll apply it later if we discover
this is actually a multicast address. */
if ((mcs = strrchr(host, '/')) != NULL &&
((strchr(host, ':') != NULL) || (mcs != strchr(host, '/')))) {
*mcs = 0;
#ifdef MULTICAST
ttl = atoi(mcs);
#endif
}
/* If a port number appears in the name, scan it and lop
it off. We allow a slash as well as the documented colon
as the delimiter to avoid confusing users familiar with
VAT. */
if ((mcs = strrchr(host, ':')) != NULL ||
(mcs = strrchr(host, '/')) != NULL) {
*mcs++ = 0;
curport = atoi(mcs);
}
/* If it's a valid IP number, use it. Otherwise try to look
up as a host name. */
if ((naddr = inet_addr(host)) != -1) {
} else {
h = gethostbyname(host);
if (h == 0) {
fprintf(stderr, "%s: unknown host\n", host);
return FALSE;
}
bcopy((char *) h->h_addr, (char *) &naddr, sizeof naddr);
}
#ifdef MULTICAST
if (!IN_MULTICAST(naddr)) {
ttl = 0;
}
#endif
d = (struct destination *) malloc(sizeof(struct destination));
d->dnext = NULL;
d->server = host;
bcopy((char *) &naddr, (char *) &(d->name.sin_addr), sizeof naddr);
bcopy((char *) &naddr, (char *) &(d->ctrl.sin_addr), sizeof naddr);
bcopy(curkey, d->deskey, 9);
bcopy(currtpkey, d->rtpdeskey, 9);
bcopy(curvatkey, d->vatdeskey, 9);
bcopy(curideakey, d->ideakey, 17);
bcopy(curpgpkey, d->pgpkey, 17);
d->blowfish_spec = curblowfish_spec;
#ifdef CRYPTO
bcopy(&curblowfishkey, &(d->blowfishkey), sizeof(BF_KEY));
#endif
d->otp = curotp;
d->time_to_live = ttl;
d->name.sin_family = AF_INET;
d->name.sin_port = htons(curport);
d->ctrl.sin_family = AF_INET;
d->ctrl.sin_port = htons(curport + 1);
if (dests == NULL) {
dests = d;
} else {
dtail->dnext = d;
}
dtail = d;
/* Send initial status and identity message in
the correct protocol. */
if (rtp) {
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
sdes, sdesl, TRUE) < 0) {
perror("sending initial RTCP SDES packet");
return FALSE;
}
} else if (vat) {
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
vatid, vatidl, FALSE) < 0) {
perror("sending initial VAT ID control packet");
return FALSE;
}
} else {
char pid = sdes[0];
/* Set Speak Freely protocol flag in packet */
sdes[0] = (sdes[0] & 0x3F) | (1 << 6);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
sdes, sdesl, TRUE) < 0) {
perror("sending initial Speak Freely SDES control packet");
return FALSE;
}
sdes[0] = pid;
}
#ifdef CRYPTO
if (pgpsb != NULL) {
int i;
/* "What I tell you three times is true". When we change
over to a TCP connection bracketing the UDP sound data
transmission, we can send this just once, knowing it has
arrived safely. */
for (i = 0; i < 3; i++) {
if (sendto(sock, (char *) pgpsb, (sizeof(struct soundbuf) - BUFL) + pgpsbl,
0, (struct sockaddr *) &(d->name), sizeof d->name) < 0) {
perror("sending PGP session key");
break;
}
}
/* No need to sleep once ack from far end confirms key decoded. */
sleep(7);
}
#endif
return TRUE;
}
/* TIMERTICK -- Timer tick signal-catching function. */
static void timertick()
{
struct destination *d;
for (d = dests; d != NULL; d = d->dnext) {
#ifdef MULTICAST
if (IN_MULTICAST(d->name.sin_addr.s_addr)) {
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
(char *) &(d->time_to_live), sizeof d->time_to_live);
if (debugging) {
fprintf(stderr, "Multicasting ID/SDES with scope of %d to %s.\n",
d->time_to_live, inet_ntoa(d->name.sin_addr));
}
}
#endif
if (rtp) {
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
sdes, sdesl, TRUE) < 0) {
perror("sending RTCP SDES packet");
}
} else if (vat) {
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
vatid, vatidl, FALSE) < 0) {
perror("sending VAT ID control packet");
}
} else {
char pid = sdes[0];
/* Set Speak Freely protocol flag in packet */
sdes[0] = (sdes[0] & 0x3F) | (1 << 6);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
sdes, sdesl, TRUE) < 0) {
perror("sending Speak Freely SDES control packet");
}
sdes[0] = pid;
}
}
ularm(TimerStep);
signal(SIGALRM, timertick); /* Reset signal to handle timeout */
}
/* SENDPKT -- Send a message to all active destinations. */
static int sendpkt(sb)
struct soundbuf *sb;
{
struct destination *d;
int pktlen;
if (gsmcompress && !toasted) {
gsmcomp(sb);
}
if (adpcmcompress && !toasted) {
adpcmcomp(sb);
}
if (lpccompress && !toasted) {
lpccomp(sb);
}
if (lpc10compress && !toasted) {
lpc10comp(sb);
}
if (hasFace) {
sb->compression |= fFaceOffer;
}
pktlen = sb->buffer.buffer_len + (sizeof(struct soundbuf) - BUFL);
if (vat) {
pktlen = vatout(sb, 0L, timestamp, spurt);
timestamp += sound_packet;
}
if (rtp) {
pktlen = rtpout(sb, ssrc, timestamp, seq, spurt);
seq++;
timestamp += sound_packet;
}
spurt = FALSE; /* Not the start of a talk spurt */
for (d = dests; d != NULL; d = d->dnext) {
#ifdef MULTICAST
if (IN_MULTICAST(d->name.sin_addr.s_addr)) {
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
(char *) &(d->time_to_live), sizeof d->time_to_live);
if (debugging) {
fprintf(stderr, "Multicasting with scope of %d to %s.\n",
d->time_to_live, inet_ntoa(d->name.sin_addr));
}
}
#endif
#ifdef CRYPTO
if (d->deskey[0] || d->ideakey[0] || d->blowfish_spec ||
d->pgpkey[0] || d->otp != NULL) {
soundbuf ebuf;
int i;
LONG slen, plen = 0;
bcopy(sb, &ebuf, pktlen);
slen = ebuf.buffer.buffer_len;
/* DES encryption. */
if (d->deskey[0]) {
if (rtp || vat) {
int vlen = pktlen;
des_cblock key;
des_key_schedule sched;
des_cblock ivec;
/* If we're DES encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire DES frame is sent. If this is an RTP
packet, we may have to set the Pad bit in the
header and include a count of pad bytes at the end
of the packet. */
bzero(ivec, 8);
pktlen = (pktlen + 7) & (~7);
if (debugging) {
fprintf(stderr, "Encrypting %d VAT/RTP bytes with DES key.\r\n",
pktlen);
}
if (pktlen > vlen) {
bzero(((char *) &ebuf) + vlen, pktlen - vlen);
}
if (rtp && (pktlen > vlen)) {
char *p = (char *) &ebuf;
p[0] |= 0x20; /* Set pad bytes present bit */
p[pktlen - 1] = pktlen - vlen; /* Set pad count at end */
}
des_set_key((des_cblock *) ((rtp ? d->rtpdeskey : d->vatdeskey) + 1), sched);
des_ncbc_encrypt((des_cblock *) &ebuf,
(des_cblock *) &ebuf, pktlen, sched,
(des_cblock *) ivec, DES_ENCRYPT);
if (vat) {
pktlen = vlen;
}
} else {
setkey(d->deskey + 1);
/* If we're DES encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire DES frame is sent. */
slen = (slen + 7) & (~7);
pktlen = slen + (sizeof(struct soundbuf) - BUFL);
if (debugging) {
fprintf(stderr, "Encrypting %d bytes with DES key.\r\n",
slen);
}
for (i = 0; i < slen; i += 8) {
/* Apply cipher block chaining within the packet. */
if (i > 0) {
int j;
for (j = 0; j < 8; j++) {
ebuf.buffer.buffer_val[(i + j)] ^=
ebuf.buffer.buffer_val[(i + j) - 8];
}
}
endes(ebuf.buffer.buffer_val + i);
}
ebuf.compression |= fEncDES;
}
}
/* IDEA encryption. */
if (d->ideakey[0]) {
unsigned short iv[4];
bzero(iv, sizeof(iv));
initcfb_idea(iv, d->ideakey + 1, FALSE);
/* If we're IDEA encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire IDEA frame is sent. */
slen = (slen + 7) & (~7);
pktlen = slen + (sizeof(struct soundbuf) - BUFL);
if (debugging) {
fprintf(stderr, "Encrypting %d bytes with IDEA key.\r\n",
slen);
}
ideacfb(ebuf.buffer.buffer_val, slen);
close_idea();
ebuf.compression |= fEncIDEA;
}
/* Blowfish encryption. */
if (d->blowfish_spec) {
unsigned char iv[8];
bzero(iv, sizeof(iv));
/* If we're Blowfish encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire Blowfish frame is sent. */
slen = (slen + 7) & (~7);
pktlen = slen + (sizeof(struct soundbuf) - BUFL);
if (debugging) {
fprintf(stderr, "Encrypting %d bytes with Blowfish key.\r\n",
slen);
}
BF_cbc_encrypt((unsigned char *) ebuf.buffer.buffer_val,
(unsigned char *) ebuf.buffer.buffer_val,
slen, &(d->blowfishkey), iv, BF_ENCRYPT);
ebuf.compression |= fEncBF;
}
/* PGP encryption. */
if (d->pgpkey[0]) {
unsigned short iv[4];
bzero(iv, sizeof(iv));
initcfb_idea(iv, d->pgpkey + 1, FALSE);
/* If we're PGP IDEA encrypting we must round the size of
the data to be sent to be a multiple of 8 so that
the entire IDEA frame is sent. */
slen = (slen + 7) & (~7);
pktlen = slen + (sizeof(struct soundbuf) - BUFL);
if (debugging) {
fprintf(stderr, "Encrypting %d bytes with PGP key.\r\n",
slen);
}
ideacfb(ebuf.buffer.buffer_val, slen);
close_idea();
ebuf.compression |= fEncPGP;
}
/* Key file encryption. */
if (d->otp != NULL) {
if (debugging) {
fprintf(stderr, "Encrypting %d bytes with key file.\r\n",
slen);
}
for (i = 0; i < slen; i ++) {
ebuf.buffer.buffer_val[i] ^= d->otp[i];
}
ebuf.compression |= fEncOTP;
}
if (!rtp && !vat) {
if (lpc10compress && !toasted) {
pktlen = lpc10stuff(&ebuf, pktlen);
if (robust > 1) {
ebuf.compression |= fCompRobust;
roSeq(&ebuf) = rseq;
rseq = (rseq + 1) & 0xFF;
}
}
ebuf.compression = htonl(ebuf.compression);
ebuf.buffer.buffer_len = htonl(ebuf.buffer.buffer_len);
}
for (i = 0; i < (lpc10compress ? robust : 1); i++) {
if (sendto(sock, (char *) &ebuf, pktlen,
0, (struct sockaddr *) &(d->name), sizeof d->name) < 0) {
perror("sending encrypted audio");
return FALSE;
}
}
} else
#endif
{
int i, wasf = FALSE;
if (!vat && !rtp) {
wasf = TRUE;
if (lpc10compress && !toasted) {
pktlen = lpc10stuff(sb, pktlen);
if (robust > 1) {
sb->compression |= fCompRobust;
sb->buffer.buffer_len |= ((long) rseq) << 24;
rseq = (rseq + 1) & 0xFF;
}
}
sb->compression = htonl(sb->compression);
sb->buffer.buffer_len = htonl(sb->buffer.buffer_len);
}
for (i = 0; i < (lpc10compress ? robust : 1); i++) {
if (sendto(sock, (char *) sb, pktlen,
0, (struct sockaddr *) &(d->name), sizeof d->name) < 0) {
perror("sending audio");
return FALSE;
}
}
if (wasf) {
sb->compression = ntohl(sb->compression);
sb->buffer.buffer_len = ntohl(sb->buffer.buffer_len);
if (sb->compression & fCompRobust) {
sb->compression &= ~fCompRobust;
sb->buffer.buffer_len &= 0xFFFFFFL;
}
if ((sb->compression & fCompLPC10) &&
(sb->buffer.buffer_len > 16)) {
pktlen += sizeof(sb->sendinghost);
}
}
}
}
return TRUE;
}
/* GETAUDIO -- Open audio input. If audio hardware is half duplex,
we may have to mute output in progress. */
static int getaudio()
{
int i;
#ifdef HALF_DUPLEX
struct soundbuf hdreq;
struct sockaddr_in name;
/* If we're afflicted with half-duplex hardware, make several
attempts to mute the output device and open audio input.
We send a mute even if the output isn't active as a courtesy
to the output program; if a later attempt to acquire output
fails, he'll know it was a result of being muted. */
for (i = 5; i > 0; i--) {
hdreq.compression = htonl(fProtocol | fHalfDuplex | fHalfDuplexMute);
strcpy(hdreq.sendinghost, hostname);
hdreq.buffer.buffer_len = 0;
bcopy((char *) &localhost, (char *) &(name.sin_addr), sizeof localhost);
name.sin_family = AF_INET;
name.sin_port = htons(Internet_Port);
if (debugging) {
fprintf(stderr,"Sending fHalfDuplex | fHalfDuplexMute Request\n");
}
if (sendto(sock, &hdreq, (sizeof(struct soundbuf) - BUFL),
0, (struct sockaddr *) &name, sizeof name) < 0) {
perror("sending half-duplex mute request");
}
sf_usleep(50000L);
if (soundinit(O_RDONLY)) {
halfDuplexMuted = TRUE;
break;
}
}
/* If we failed to initialise, send a resume just in case
one of our mute requests made it through anyway. */
if (i <= 0) {
hdreq.compression = htonl(fProtocol | fHalfDuplex | fHalfDuplexResume);
if (sendto(sock, &hdreq, (sizeof(struct soundbuf) - BUFL),
0, (struct sockaddr *) &name, sizeof name) < 0) {
perror("sending half-duplex resume request");
}
}
#else
i = soundinit(O_RDONLY);
#endif
if (i <= 0) {
fprintf(stderr, "%s: unable to initialise audio.\n", progname);
return FALSE;
}
havesound = TRUE;
if (agc) {
soundrecgain(rgain); /* Set initial record level */
}
return TRUE;
}
/* FREEAUDIO -- Release sound input device, sending a resume to
the output device if we've muted it due to
half-duplex hardware. */
static void freeaudio()
{
if (havesound) {
if (debugging) {
fprintf(stderr, "Restoring audio modes at release.\n");
}
soundterm();
havesound = FALSE;
}
#ifdef HALF_DUPLEX
/* When bailing out, make sure we don't leave the output
muted. */
if (halfDuplexMuted) {
struct soundbuf hdreq;
struct sockaddr_in name;
hdreq.compression = htonl(fProtocol | fHalfDuplex | fHalfDuplexResume);
strcpy(hdreq.sendinghost, hostname);
hdreq.buffer.buffer_len = 0;
bcopy((char *) &localhost, (char *) &(name.sin_addr), sizeof localhost);
name.sin_family = AF_INET;
name.sin_port = htons(Internet_Port);
if (sendto(sock, &hdreq, (sizeof(struct soundbuf) - BUFL),
0, (struct sockaddr *) &name, sizeof name) < 0) {
perror("sending half-duplex resume request");
}
halfDuplexMuted = FALSE;
}
#endif
}
/* EXITING -- Catch as many program termination signals as
possible and restore initial audio modes before
we exit. */
static void exiting()
{
struct destination *d;
freeaudio();
if (audioDumpFile != NULL) {
fclose(audioDumpFile);
}
#ifdef PUSH_TO_TALK
if (rawmode) {
fprintf(stderr, "\r \r");
#ifdef USE_CURSES
nocbreak();
echo();
endwin();
fcntl(fileno(stdin), F_SETFL, 0);
#else
tty_normode();
fcntl(fileno(stdin), F_SETFL, 0);
#endif
}
#endif
if (rtp) {
for (d = dests; d != NULL; d = d->dnext) {
char v[1024];
int l;
l = rtp_make_bye(v, ssrc, "Exiting Speak Freely", TRUE);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
v, l, TRUE) < 0) {
perror("sending RTCP BYE packet");
}
}
} else if (vat) {
for (d = dests; d != NULL; d = d->dnext) {
char v[16];
int l;
l = makevatdone(v, 0L);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
v, l, FALSE) < 0) {
perror("sending VAT DONE control packet");
}
}
} else {
for (d = dests; d != NULL; d = d->dnext) {
char v[1024];
int l;
l = rtp_make_bye(v, ssrc, "Exiting Speak Freely", TRUE);
v[0] = (v[0] & 0x3F) | (1 << 6);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
v, l, TRUE) < 0) {
perror("sending Speak Freely BYE packet");
}
}
}
exit(0);
}
/* CHANGEMODE -- Reset state when talk/pause/quiet mode changes. */
static void changemode()
{
if (talking) {
spurt = TRUE;
/* Discard all backlog sound input. */
soundflush();
} else {
sqwait = 0; /* Reset squelch delay */
}
}
/* TERMCHAR -- Check for special characters from console when
in raw mode. */
static void termchar(ch)
int ch;
{
#define Ctrl(x) ((x) - '@')
if (ch == 'q' || ch == 'Q' || ch == 27 ||
ch == Ctrl('C') || ch == Ctrl('D')) {
exiting();
}
}
/* CHATCHAR -- Test for text chat request character. */
static int chatchar(ch)
int ch;
{
if ((!rtp && !vat) && ch == '.') {
char s[133];
if (rawmode) {
#ifdef USE_CURSES
nocbreak();
echo();
endwin();
#else
tty_normode();
#endif
}
fcntl(fileno(stdin), F_SETFL, 0);
if (push) {
talking = 0;
}
fprintf(stderr, "\rChat: ");
while (TRUE) {
if (fgets(s, (sizeof s) - 1, stdin) != NULL) {
struct destination *d;
while (strlen(s) > 0 && isspace(s[strlen(s) - 1])) {
s[strlen(s) - 1] = 0;
}
for (d = dests; d != NULL; d = d->dnext) {
char v[256];
int l;
l = rtp_make_app(v, ssrc, TRUE, RTCP_APP_TEXT_CHAT, s);
/* Set Speak Freely protocol flag in packet */
v[0] = (v[0] & 0x3F) | (1 << 6);
if (sendrtpctrl(d, (struct sockaddr *) &(d->ctrl),
v, l, TRUE) < 0) {
perror("sending text chat packet");
}
}
/*
fprintf(stderr, "\nOut of chat mode, input(%d) = \"%s\"\n", strlen(s), s);
*/
fprintf(stderr, talking ? (squelch > 0 ?
"\rQuiet: " : "\rTalk: ") : "\rPause: ");
if (rawmode) {
#ifdef USE_CURSES
initscr();
noecho();
cbreak();
#else
tty_rawmode();
#endif
}
changemode();
break;
}
}
fcntl(fileno(stdin), F_SETFL, talking ? O_NDELAY : 0);
return TRUE;
}
return FALSE;
}
/* SENDFILE -- Send a file or, if the file name is NULL or a
single period, send real-time sound input. */
static int sendfile(f)
char *f;
{
soundbuf netbuf;
#define buf netbuf.buffer.buffer_val
int bread, nread, lread;
FILE *afile = NULL;
static int firstTime = TRUE;
if (firstTime) {
firstTime = FALSE;
signal(SIGHUP, exiting); /* Set signal to handle termination */
signal(SIGINT, exiting); /* Set signal to handle termination */
signal(SIGTERM, exiting); /* Set signal to handle termination */
signal(SIGALRM, timertick); /* Set signal to handle timer */
ularm(TimerStep);
}
/* Compute the number of sound samples needed to fill a
packet of TINY_PACKETS bytes. */
if (rtp) {
sound_packet = (gsmcompress | lpccompress | adpcmcompress) ? (160 * 4)
: 320;
} else if (vat) {
sound_packet = (gsmcompress | lpccompress | adpcmcompress) ? (160 * 4)
: 320;
} else {
sound_packet = ((TINY_PACKETS - ((sizeof(soundbuf) - BUFL))) *
(compressing ? 2 : 1));
if (gsmcompress) {
sound_packet = compressing ? 3200 : 1600;
} else if (adpcmcompress) {
sound_packet *= 2;
sound_packet -= 4; /* Leave room for state at the end */
} else if (lpccompress) {
sound_packet = 10 * LPC_FRAME_SIZE;
} else if (lpc10compress) {
sound_packet = compressing ? 3600 : 1800;
}
}
#ifdef SHOW_PACKET_SIZE
printf("Samples per packet = %d\n", sound_packet);
#endif
lread = sound_packet;
strcpy(netbuf.sendinghost, hostname);
if (f != NULL && (strcmp(f, ".") != 0)) {
char magic[4];
afile = fopen(f, "r");
if (afile == NULL) {
fprintf(stderr, "Unable to open sound file %s.\n", f);
return 2;
}
toasted = FALSE;
if ((strlen(f) > 4) && (strcmp(f + (strlen(f) - 4), ".gsm") == 0)) {
toasted = TRUE;
lread = 33 * ((rtp || vat) ? 4 : 10);
sound_packet = (160 * lread) / 33;
} else {
/* If the file has a Sun .au file header, skip it.
Note that we still blithely assume the file is
8-bit ISDN u-law encoded at 8000 samples per
second. */
fread(magic, 1, sizeof(long), afile);
if (bcmp(magic, ".snd", 4) == 0) {
long startpos;
fread(&startpos, sizeof(long), 1, afile);
fseek(afile, ntohl(startpos), 0);
} else {
fseek(afile, 0L, 0);
}
}
}
/* Send a file */
if (afile) {
int tlast = FALSE;
#ifdef BSD_like
struct timeb t1, t2, tl;
#else
struct timeval t1, t2, tl;
#endif
long et, corr = 0;
while (
#ifdef BSD_like
ftime(&t1),
#else
gettimeofday(&t1, NULL),
#endif
(bread = nread =
fread(buf + (toasted ? 2 : 0), 1,
lread,
afile)) > 0) {
netbuf.compression = fProtocol | (ring ? (fSetDest | fDestSpkr) : 0);
ring = FALSE;
netbuf.compression |= debugging ? fDebug : 0;
netbuf.compression |= loopback ? fLoopBack : 0;
if (toasted) {
*((short *) netbuf.buffer.buffer_val) = bread = (160 * nread) / 33;
netbuf.buffer.buffer_len = lread + sizeof(short);
netbuf.compression |= fCompGSM;
} else {
if (compressing) {
int is = nread, os = nread / 2;
rate_flow(buf, buf, &is, &os);
nread = os;
netbuf.compression |= fComp2X;
}
netbuf.buffer.buffer_len = nread;
}
if (!sendpkt(&netbuf)) {
fclose(afile);
toasted = FALSE;
exiting();
}
/* The following code is needed because when we're reading
sound from a file, as opposed to receiving it in real
time from the CODEC, we must meter out the samples
at the rate they will actually be played by the destination
machine. For a 8000 samples per second, this amounts
to 125 microseconds per sample, minus the time we spent
compressing the data (which is substantial for GSM) and
encrypting the data.
What we're trying to accomplish here is to maintain a
predictable time between packets equal to the sample
rate. This is, of course, very difficult since system
performance and instantaneous load changes the relationship
between the compute time per packet and the length of
the sound it represents. What the code below does is
measure the actual time between packets, compare it to
the desired time, then use the error signal as input
to an exponentially smoothed moving average with P=0.1
which adjusts the time, in microseconds, we wait between
packets. This quickly adapts to the correct wait time
based on system performance and options selected, while
minimsing perturbations due to momentary changes in
system load. Due to incompatibilities between System V
and BSD-style gettimeofday(), we do this differently
for the two styles of system. If the system conforms
to neither convention, we just guess based on the number
of samples in the packet and a fudge factor. */
#define kOverhead 8000
#ifdef BSD_like
ftime(&t2);
if (tlast) {
long dt = 1000 * (((t2.time - tl.time) * 1000) +
(t2.millitm - tl.millitm)),
atime = bread * 125, error;
error = atime - dt;
if (error > atime / 2) {
error = atime / 2;
} else if (error < -(atime / 2)) {
error = -(atime / 2);
}
corr = corr - 0.1 * error;
et = atime - corr;
#ifdef DEBUG_SOUNDFILE_WAIT
printf("Packet time %d Delta t = %d Error = %d, Corr = %d, Wait = %d\n",
atime, dt, error, corr, et);
#endif
} else {
#ifdef __FreeBSD__
corr = 80000;
#else
corr = 8000;
#endif
et = ((bread * 125) -
1000 * (((t2.time - t1.time) * 1000) +
(t2.millitm - t1.millitm))) - kOverhead;
}
tl = t2;
tlast = TRUE;
#else
gettimeofday(&t2, NULL);
if (tlast) {
long dt = (t2.tv_sec - tl.tv_sec) * 1000000 +
(t2.tv_usec - tl.tv_usec),
atime = bread * 125, error;
error = atime - dt;
if (error > atime / 2) {
error = atime / 2;
} else if (error < -(atime / 2)) {
error = -(atime / 2);
}
corr = corr - 0.1 * error;
et = atime - corr;
#ifdef DEBUG_SOUNDFILE_WAIT
printf("Packet time %d Delta t = %d Error = %d, Corr = %d, Wait = %d\n",
atime, dt, error, corr, et);
#endif
} else {
et = ((bread * 125) -
(t2.tv_sec - t1.tv_sec) * 1000000 +
(t2.tv_usec - t1.tv_usec)) - kOverhead;
}
tl = t2;
tlast = TRUE;
#endif
if (et > 0) {
sf_usleep(et);
}
if (debugging && !vat && !rtp) {
fprintf(stderr, "Sent %d samples from %s in %d bytes.\r\n",
bread, f, ntohl(netbuf.buffer.buffer_len));
}
}
if (debugging) {
fprintf(stderr, "Sent sound file %s.\r\n", f);
}
fclose(afile);
toasted = FALSE;
} else {
/* Send real-time audio. */
#ifdef PUSH_TO_TALK
if (push) {
char c;
int l;
fprintf(stderr,
"Space bar switches talk/pause, Esc or \"q\" to quit, \".\" for chat text\nPause: ");
fflush(stderr);
#ifdef USE_CURSES
initscr();
noecho();
cbreak();
#else
tty_rawmode();
#endif
rawmode = TRUE;
while (TRUE) {
while (TRUE) {
if (l = read(fileno(stdin), &c, 1) == 1 || errno != EINTR) {
break;
}
}
if (l == 1 && !chatchar(c)) {
break;
}
}
if (l != 1) {
perror("waiting for first Pause/Talk character");
}
termchar(c);
fprintf(stderr, squelch > 0 ? "\rQuiet: " : "\rTalk: ");
fcntl(fileno(stdin), F_SETFL, O_NDELAY);
talking = TRUE;
spurt = TRUE;
}
#endif
/* Send real-time sound. */
#ifdef Solaris
setaubufsize(sound_packet);
#endif
if (!getaudio()) {
fprintf(stderr, "Unable to initialise audio.\n");
return 2;
}
changemode();
while (TRUE) {
int soundel = 0;
unsigned char *bs = (unsigned char *) buf;
if (havesound) {
/* Obtain a packet's worth of real-time audio from
the audio input hardware. */
soundel = soundgrab(buf, sound_packet);
/* If a raw audio dump file (-w option) is specified,
dump whatever we got from the audio input to the
designated file. This can come in handy when
debugging audio drivers which don't provide samples
in the expected 8 bit mu-law mode. */
if ((audioDumpFile != NULL) && (soundel > 0)) {
fwrite(buf, 1, soundel, audioDumpFile);
}
}
#ifdef PUSH_TO_TALK
while (push) {
char c;
int rlen;
if ((rlen = read(fileno(stdin), &c, 1)) > 0
#ifdef HEWLETT_PACKARD
|| HPTermHookChar
#endif
) {
#ifdef HEWLETT_PACKARD
c = HPTermHookChar;
#endif
if (rlen > 0 && chatchar(c)) {
continue;
}
termchar(c);
talking = !talking;
fflush(stderr);
#ifdef HALF_DUPLEX
/* For half-duplex, acquire and release the
audio device at each transition. This lets
us mute the output only while in Talk mode. */
if (talking) {
if (!getaudio()) {
fprintf(stderr, "Audio device busy.\n");
talking = FALSE;
}
} else {
freeaudio();
}
#endif
fprintf(stderr, talking ? (squelch > 0 ?
"\rQuiet: " : "\rTalk: ") : "\rPause: ");
fcntl(fileno(stdin), F_SETFL, talking ? O_NDELAY : 0);
#ifdef OBSOLETE
if (talking) {
spurt = TRUE;
/* Discard all backlog sound input. */
soundflush();
fprintf(stderr, "\n**flush**\n");
} else {
sqwait = 0; /* Reset squelch delay */
}
#endif
changemode();
break;
} else {
if (rlen == -1 && errno == EINTR) {
continue;
}
break;
}
}
#endif
if (soundel > 0) {
register unsigned char *start = bs;
register int j;
int squelched = (squelch > 0), osl = soundel;
/* If entire buffer is less than squelch, ditch it. If
we haven't received sqdelay samples since the last
squelch event, continue to transmit. */
if (sqdelay > 0 && sqwait > 0) {
if (debugging) {
printf("Squelch countdown: %d samples left.\n",
sqwait);
}
sqwait -= soundel;
squelched = FALSE;
#ifdef PUSH_TO_TALK
if (sqwait <= 0 && push) {
fprintf(stderr, "\rQuiet: ");
}
#endif
} else if (squelch > 0) {
for (j = 0; j < soundel; j++) {
#ifdef USQUELCH
if (((*start++ & 0x7F) ^ 0x7F) > squelch)
#else
int samp = audio_u2s(*start++);
if (samp < 0) {
samp = -samp;
}
if (samp > squelch)
#endif
{
squelched = FALSE;
#ifdef PUSH_TO_TALK
if (sqwait <= 0 && push) {
fprintf(stderr, "\rTalk: ");
}
#endif
sqwait = sqdelay;
break;
}
}
}
if (squelched) {
if (debugging) {
printf("Entire buffer squelched.\n");
}
spurt = TRUE;
} else {
netbuf.compression = fProtocol | (ring ? (fSetDest | fDestSpkr) : 0);
netbuf.compression |= debugging ? fDebug : 0;
netbuf.compression |= loopback ? fLoopBack : 0;
/* If automatic gain control is enabled,
ride the gain pot to fill the dynamic range
optimally. */
if (agc) {
register unsigned char *start = bs;
register int j;
long msamp = 0;
for (j = 0; j < soundel; j++) {
int s = audio_u2s(*start++);
msamp += (s < 0) ? -s : s;
}
msamp /= soundel;
if (msamp < 6000) {
if (rgain < 100) {
soundrecgain(++rgain);
}
} else if (msamp > 7000) {
if (rgain > 1) {
soundrecgain(--rgain);
}
}
}
ring = FALSE;
if (compressing) {
int is = soundel, os = soundel / 2;
rate_flow(buf, buf, &is, &os);
soundel = os;
netbuf.compression |= fComp2X;
}
netbuf.buffer.buffer_len = soundel;
if (!sendpkt(&netbuf)) {
exiting();
}
if (debugging && !vat && !rtp) {
fprintf(stderr, "Sent %d audio samples in %d bytes.\r\n",
osl, soundel);
}
}
} else {
sf_usleep(100000L); /* Wait for some sound to arrive */
}
}
}
return 0;
}
/* MAKESESSIONKEY -- Generate session key with optional start
key. If mode is TRUE, the key will be
translated to a string, otherwise it is
returned as 16 binary bytes. */
static void makeSessionKey(key, seed, mode)
char *key, *seed;
int mode;
{
int j, k;
struct MD5Context md5c;
char md5key[16], md5key1[16];
char s[1024];
s[0] = 0;
if (seed != NULL) {
strcat(s, seed);
}
/* The following creates a seed for the session key generator
based on a collection of volatile and environment-specific
information unlikely to be vulnerable (as a whole) to an
exhaustive search attack. If one of these items isn't
available on your machine, replace it with something
equivalent or, if you like, just delete it. */
sprintf(s + strlen(s), "%u", getpid());
sprintf(s + strlen(s), "%u", getppid());
V getcwd(s + strlen(s), 256);
sprintf(s + strlen(s), "%u", clock());
V cuserid(s + strlen(s));
sprintf(s + strlen(s), "%u", time(NULL));
#ifdef Solaris
sysinfo(SI_HW_SERIAL,s + strlen(s), 12);
#else
sprintf(s + strlen(s), "%u", gethostid());
#endif
getdomainname(s + strlen(s), 256);
gethostname(s + strlen(s), 256);
sprintf(s + strlen(s), "%u", getuid());
sprintf(s + strlen(s), "%u", getgid());
MD5Init(&md5c);
MD5Update(&md5c, s, strlen(s));
MD5Final(md5key, &md5c);
sprintf(s + strlen(s), "%u", (time(NULL) + 65121) ^ 0x375F);
MD5Init(&md5c);
MD5Update(&md5c, s, strlen(s));
MD5Final(md5key1, &md5c);
#ifdef CRYPTO
init_idearand(md5key, md5key1, time(NULL));
#define nextrand idearand()
#else
#define nextrand (md5key[j] ^ md5key1[j])
#endif
if (mode) {
for (j = k = 0; j < 16; j++) {
unsigned char rb = nextrand;
#define Rad16(x) ((x) + 'A')
key[k++] = Rad16((rb >> 4) & 0xF);
key[k++] = Rad16(rb & 0xF);
#undef Rad16
if (j & 1) {
key[k++] = '-';
}
}
key[--k] = 0;
} else {
for (j = 0; j < 16; j++) {
key[j] = nextrand;
}
}
#ifdef CRYPTO
close_idearand();
#endif
#undef nextrand
}
/* PROG_NAME -- Extract program name from argv[0]. */
static char *prog_name(arg)
char *arg;
{
char *cp = strrchr(arg, '/');
return (cp != NULL) ? cp + 1 : arg;
}
/* USAGE -- Print how-to-call information. */
static void usage()
{
V fprintf(stderr, "%s -- Speak Freely sound sender.\n", progname);
V fprintf(stderr, " %s.\n", Relno);
V fprintf(stderr, "\n");
V fprintf(stderr, "Usage: %s hostname[:port] [options] [ file1 / . ]...\n", progname);
V fprintf(stderr, "Options: (* indicates defaults)\n");
#ifdef PUSH_TO_TALK
V fprintf(stderr, " -A Always transmit unless squelched\n");
V fprintf(stderr, " * -B Push to talk using keyboard\n");
#endif
#ifdef CRYPTO
V fprintf(stderr, " -BFkey Blowfish encrypt with key\n");
#endif
V fprintf(stderr, " -C Compress subsequent sound\n");
V fprintf(stderr, " -D Enable debug output\n");
#ifdef CRYPTO
V fprintf(stderr, " -E[key] Emit session key string\n");
#endif
V fprintf(stderr, " -F ADPCM compression\n");
V fprintf(stderr, " -G Automatic gain control\n");
#ifdef CRYPTO
V fprintf(stderr, " -Ikey IDEA encrypt with key\n");
V fprintf(stderr, " -Kkey DES encrypt with key\n");
#endif
V fprintf(stderr, " -L Remote loopback\n");
V fprintf(stderr, " -LPC LPC compression\n");
V fprintf(stderr, " -LPC10Rn LPC-10 compression, n copies\n");
V fprintf(stderr, " * -M Manual record gain control\n");
V fprintf(stderr, " -N Do not compress subsequent sound\n");
#ifdef CRYPTO
V fprintf(stderr, " -Ofile Use file as key file\n");
#endif
V fprintf(stderr, " -Phostname[:port] Party line, add host to list\n");
V fprintf(stderr, " * -Q Disable debug output\n");
V fprintf(stderr, " -R Ring--force volume, output to speaker\n");
V fprintf(stderr, " -RTP Use Internet Real-Time Protocol\n");
V fprintf(stderr, " -Sn,t Squelch at level n (0-32767), timeout t milliseconds\n");
V fprintf(stderr, " * -T Telephone (GSM) compression\n");
#ifdef OBSOLETE
V fprintf(stderr, " -TC GSM with corrected x86 byte order\n");
#endif
#ifdef BYTE_SWAP_DEBUG
V fprintf(stderr, " -TD Debug: send GSM with swapped byte order\n");
#endif
V fprintf(stderr, " -U Print this message\n");
V fprintf(stderr, " -VAT Use VAT protocol\n");
V fprintf(stderr, " -Wfile Write raw audio input to file\n");
#ifdef AUDIO_DEVICE_FILE
V fprintf(stderr, " -Yindev[:ctldev] Override default audio device file name or specify open #fd\n");
#endif
#ifdef CRYPTO
V fprintf(stderr, " -Z\"user..\" Send PGP session key for user(s)\n");
#endif
V fprintf(stderr, "\n");
V fprintf(stderr, "by John Walker\n");
V fprintf(stderr, " http://www.fourmilab.ch/\n");
#ifndef CRYPTO
V fprintf(stderr, "\n");
V fprintf(stderr, "Note: This version of Speak Freely was built with\n");
V fprintf(stderr, " encryption code removed to permit redistribution\n");
V fprintf(stderr, " without concern for export control and regulations\n");
V fprintf(stderr, " concerning encryption technology in some jurisdictions.\n");
V fprintf(stderr, " You can download a version of Speak Freely with\n");
V fprintf(stderr, " full encryption from:\n");
V fprintf(stderr, " http://www.fourmilab.ch/speakfree/unix/\n");
#endif
}
/* Main program. */
main(argc, argv)
int argc;
char *argv[];
{
int i, j, k, l, sentfile = 0;
FILE *fp;
struct MD5Context md5c;
static lpcstate_t lpcc;
char md5key[16], md5key1[16];
char s[1024];
progname = prog_name(argv[0]);
if (gethostname(hostname, sizeof hostname) == -1) {
strcpy(hostname, "localhost");
} else {
if (strlen(hostname) > 15) {
hostname[15] = 0;
}
}
#ifdef LINUX_FPU_FIX
__setfpucw(_FPU_IEEE); /* Mask floating point interrupts to
enable standard IEEE processing.
Current libc releases do this by
default, so this is needed only on
older libraries. */
#endif
rate_start(8000, 4000);
gsmh = gsm_create();
/* Initialise LPC decoding. */
if (!lpc_start()) {
fprintf(stderr, "Cannot allocate LPC decoding memory.\n");
return 1;
}
lpc_init(&lpcc);
lpc10init();
#ifdef sgi
usticks = 1000000 / CLK_TCK;
#endif
#ifdef HALF_DUPLEX
{
#ifndef INADDR_LOOPBACK
/* The standard local host loopback address is supposed to be
defined in <netinet/in.h>, but just in case some screwball
system doesn't, let's include our own definition here. */
#define INADDR_LOOPBACK 0x7F000001
#endif
localhost.s_addr = htonl(INADDR_LOOPBACK);
#ifdef HDX_DEBUG
fprintf(stderr, "%s: local host %s\n", progname,
inet_ntoa(localhost));
#endif
}
#endif
/* Create the socket used to send data. */
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("opening datagram socket");
return 1;
}
/* Initialise randomised packet counters. */
makeSessionKey(md5key, NULL, FALSE);
bcopy(md5key, (char *) &ssrc, sizeof ssrc);
bcopy(md5key + sizeof ssrc, (char *) ×tamp,
sizeof timestamp);
bcopy(md5key + sizeof ssrc + sizeof timestamp,
(char *) &seq, sizeof seq);
bcopy(md5key + sizeof ssrc + sizeof timestamp +
sizeof seq, &rtpdesrand, sizeof rtpdesrand);
sdesl = rtp_make_sdes(&sdes, ssrc, -1, TRUE);
/* See if there's a face image file for this user. If
so, we'll offer our face in sound packets we send. */
{ char *cp = getenv("SPEAKFREE_FACE");
FILE *facefile;
if (cp != NULL) {
if ((facefile = fopen(cp, "r")) == NULL) {
fprintf(stderr, "%s: cannot open SPEAKFREE_FACE file %s\n",
progname, cp);
} else {
fclose(facefile);
hasFace = TRUE;
}
}
}
/* Process command line options. */
for (i = 1; i < argc; i++) {
char *op, opt;
op = argv[i];
if (*op == '-') {
opt = *(++op);
if (islower(opt)) {
opt = toupper(opt);
}
switch (opt) {
#ifdef PUSH_TO_TALK
case 'A': /* -A -- Always transmit (no push to talk) */
push = FALSE;
break;
case 'B': /* -B -- Push to talk (button) */
#ifdef CRYPTO
/* -BFkey -- Set Blowfish key */
if (ucase(op[1]) == 'F') {
if (strlen(op + 2) == 0) {
curblowfish_spec = FALSE;
} else {
unsigned char bfvec[16];
curblowfish_spec = TRUE;
MD5Init(&md5c);
MD5Update(&md5c, op + 2, strlen(op + 2));
MD5Final(bfvec, &md5c);
BF_set_key(&(curblowfishkey), 16, bfvec);
if (debugging) {
fprintf(stderr, "Blowfish key:");
for (j = 0; j < 16; j++) {
fprintf(stderr, " %02X", (bfvec[j] & 0xFF));
}
fprintf(stderr, "\n");
}
}
} else {
#endif
if (isatty(fileno(stdin))) {
push = TRUE;
}
#ifdef CRYPTO
}
#endif
break;
#endif
case 'C': /* -C -- Compress sound samples */
compressing = TRUE;
break;
case 'D': /* -D -- Enable debug output */
debugging = TRUE;
break;
#ifdef CRYPTO
case 'E': /* -E -- Emit session key and exit */
makeSessionKey(s, op + 1, TRUE);
printf("%s\n", s);
return 0;
#endif
case 'F': /* -F -- ADPCM compression */
adpcmcompress = TRUE;
gsmcompress = lpccompress = lpc10compress = FALSE;
break;
case 'G': /* -G -- Automatic gain control */
agc = TRUE;
break;
#ifdef CRYPTO
case 'I': /* -Ikey -- Set IDEA key */
if (strlen(op + 1) == 0) {
curideakey[0] = FALSE;
} else {
MD5Init(&md5c);
MD5Update(&md5c, op + 1, strlen(op + 1));
MD5Final(curideakey + 1, &md5c);
curideakey[0] = TRUE;
if (debugging) {
fprintf(stderr, "IDEA key:");
for (j = 1; j < 17; j++) {
fprintf(stderr, " %02X", (curideakey[j] & 0xFF));
}
fprintf(stderr, "\n");
}
}
break;
case 'K': /* -Kkey -- Set DES key */
desinit(1); /* Initialise the DES library */
if (strlen(op + 1) == 0) {
curkey[0] = currtpkey[0] = curvatkey[0] = FALSE;
} else {
char algorithm[16];
MD5Init(&md5c);
MD5Update(&md5c, op + 1, strlen(op + 1));
MD5Final(md5key, &md5c);
for (j = 0; j < 8; j++) {
curkey[j + 1] = (char)
((md5key[j] ^ md5key[j + 8]) & 0x7F);
}
if (debugging) {
fprintf(stderr, "DES key:");
for (j = 0; j < 8; j++) {
fprintf(stderr, " %02X", (curkey[j + 1] & 0xFF));
}
fprintf(stderr, "\n");
}
curkey[0] = TRUE;
des_string_to_key(op + 1, (des_cblock *) (curvatkey + 1));
string_DES_key(op + 1, currtpkey + 1, algorithm);
if (strcmp(algorithm, "DES-CBC") != 0) {
fprintf(stderr, "Unsupported encryption algorithm: %s. Only DES-CBC is available.\n",
algorithm);
return 2;
}
currtpkey[0] = curvatkey[0] = TRUE;
if (debugging) {
fprintf(stderr, "RTP key:");
for (j = 0; j < 8; j++) {
fprintf(stderr, " %02X", (currtpkey[j + 1] & 0xFF));
}
fprintf(stderr, "\n");
}
if (debugging) {
fprintf(stderr, "VAT key:");
for (j = 0; j < 8; j++) {
fprintf(stderr, " %02X", (curvatkey[j + 1] & 0xFF));
}
fprintf(stderr, "\n");
}
}
break;
#endif
case 'L': /* -L -- Remote loopback */
/* -LPC -- LPC compress sound */
/* -LPC10 -- LPC10 compress sound */
if (ucase(op[1]) == 'P') {
if (strlen(op) > 3 && op[3] == '1') {
lpc10compress = TRUE;
gsmcompress = adpcmcompress = lpccompress = FALSE;
robust = 1;
if (strlen(op) > 5 && ucase(op[5]) == 'R') {
robust = 2;
if (strlen(op) > 6 && isdigit(op[6])) {
robust = atoi(op + 6);
if (robust <= 0) {
robust = 1;
} else if (robust > 4) {
robust = 4;
}
}
}
} else {
lpccompress = TRUE;
gsmcompress = adpcmcompress = lpc10compress = FALSE;
}
} else {
loopback = TRUE;
}
break;
case 'M': /* -M -- Manual record gain control */
agc = FALSE;
break;
case 'N': /* -N -- Do not compress sound samples */
compressing = FALSE;
gsmcompress = FALSE;
lpccompress = FALSE;
lpc10compress = FALSE;
adpcmcompress = FALSE;
break;
#ifdef CRYPTO
case 'O': /* -Ofile -- Use file as key file */
if (op[1] == 0) {
curotp = NULL; /* Switch off key file */
} else {
fp = fopen(op + 1, "r");
if (fp == NULL) {
perror("Cannot open key file");
return 2;
}
curotp = malloc(BUFL);
if (curotp == NULL) {
fprintf(stderr, "Cannot allocate key file buffer.\n");
return 2;
}
l = fread(curotp, 1, BUFL, fp);
if (l == 0) {
/* Idiot supplied void key file. Give 'im
what he asked for: no encryption. */
curotp[0] = 0;
l = 1;
}
fclose(fp);
/* If the file is shorter than the maximum buffer
we may need to encrypt, replicate the key until
the buffer is filled. */
j = l;
k = 0;
while (j < BUFL) {
curotp[j++] = curotp[k++];
if (k >= l) {
k = 0;
}
}
}
break;
#endif
case 'P': /* -Phost -- Copy output to host */
if (!addest(op + 1)) {
return 1;
}
break;
case 'Q': /* -Q -- Disable debug output */
debugging = FALSE;
break;
case 'R': /* -R -- Ring: divert output to speaker */
/* -RTP -- Use RTP to transmit */
if (ucase(op[1]) == 'T') {
rtp = TRUE;
} else {
ring = TRUE;
}
break;
case 'S': /* -Sn,d -- Squelch at level n,
delay d milliseconds */
if (strlen(op + 1) == 0) {
squelch = DEFAULT_SQUELCH; /* Default squelch */
} else {
char *cp;
if (op[1] != ',') {
squelch = atoi(op + 1);
} else {
squelch = DEFAULT_SQUELCH;
}
cp = strchr(op + 1, ',');
if (cp != NULL) {
sqdelay = 8 * atoi(cp + 1);
}
}
break;
case 'T': /* -T -- Telephone (GSM) compression */
gsmcompress = TRUE;
lpccompress = lpc10compress = adpcmcompress = FALSE;
#ifdef OBSOLETE
if (ucase(op[1]) == 'C') {
gsm_byte_order_kludge = TRUE;
}
#endif
#ifdef BYTE_SWAP_DEBUG
/* This can be used if you absolutely have to communicate
with somebody running a version prior to 6.1e on little-endian
hardware. This brakes the byte order of GSM packets to
compensate for a bug in earlier versions. Note that you
should only set this for compatibility with old Unix
versions--the problem this works around was never in
the Windows version. */
if (ucase(op[1]) == 'D') {
gsm_byte_order_debug = TRUE;
}
#endif
break;
case 'U': /* -U -- Print usage information */
case '?': /* -? -- Print usage information */
usage();
return 0;
case 'V': /* -V -- Use VAT protocol */
vat = TRUE;
if (vatid == NULL) {
vatidl = makeVATid(&vatid, 0L);
}
break;
case 'W': /* -Wfile -- Write raw audio to file */
audioDumpFile = fopen(op + 1, "w");
if (audioDumpFile == NULL) {
perror("Cannot create raw audio dump (-w option) file");
return 2;
}
break;
#ifdef AUDIO_DEVICE_FILE
case 'Y': /* -Yindev:[ctldev] -- Specify audio input and control device file names
or #open_fd. */
devAudioInput = op + 1;
if (strchr(op + 1, ':') != NULL) {
devAudioControl = strchr(op + 1, ':') + 1;
}
break;
#endif
#ifdef CRYPTO
case 'Z': /* -Z"user1 user2..." -- Send PGP
encrypted session key to
named users */
if (op[1] == 0) {
curpgpkey[0] = FALSE;
} else {
char c[80], f[40];
FILE *kfile;
FILE *pipe;
long flen;
sprintf(f, "/tmp/.SF_SKEY%d", getpid());
sprintf(c, "pgp -fe +nomanual +verbose=0 +armor=off %s >%s", op + 1, f);
#ifdef PGP_DEBUG
fprintf(stderr, "Encoding session key with: %s\n", c);
#endif
pipe = popen(c, "w");
if (pipe == NULL) {
fprintf(stderr, "Unable to open pipe to: %s\n", c);
return 2;
} else {
makeSessionKey(curpgpkey + 1, NULL, FALSE);
#ifdef PGP_DEBUG
{
int i;
fprintf(stderr, "Session key:", hostname);
for (i = 0; i < 16; i++) {
fprintf(stderr, " %02X", curpgpkey[i + 1] & 0xFF);
}
fprintf(stderr, "\n");
}
#endif
/* The reason we start things off right with
"Special K" is to prevent the session key
(which can be any binary value) from
triggering PGP's detection of the file as
one already processed by PGP. This causes an
embarrassing question when we are attempting
to run silent. */
curpgpkey[0] = 'K';
fwrite(curpgpkey, 17, 1, pipe);
curpgpkey[0] = FALSE;
fflush(pipe);
pclose(pipe);
}
kfile = fopen(f, "r");
if (kfile == NULL) {
fprintf(stderr, "Cannot open key file %s\n", f);
} else {
fseek(kfile, 0L, 2);
flen = ftell(kfile);
rewind(kfile);
#ifdef PGP_DEBUG
fprintf(stderr, "PGP key buffer length: %d\n", flen);
#endif
if (flen > (TINY_PACKETS -
(sizeof(soundbuf) - BUFL))) {
fprintf(stderr, "Warning: PGP key message exceeds %d packet size.\n", TINY_PACKETS);
}
if (pgpsb != NULL) {
free(pgpsb);
}
pgpsb = (soundbuf *) malloc(((unsigned) flen) +
(TINY_PACKETS - (sizeof(soundbuf) - BUFL)));
if (pgpsb == NULL) {
fprintf(stderr, "Cannot allocate PGP sound buffer.\n");
fclose(kfile);
unlink(f);
return 2;
}
fread(pgpsb->buffer.buffer_val, (int) flen, 1, kfile);
pgpsbl = flen;
pgpsb->buffer.buffer_len = htonl(flen);
pgpsb->compression = htonl(fProtocol | fKeyPGP);
#ifdef SENDMD5
MD5Init(&md5c);
MD5Update(&md5c, pgpsb->buffer.buffer_val, pgpsb->buffer.buffer_len);
MD5Final(pgpsb->sendinghost, &md5c);
#else
strcpy(pgpsb->sendinghost, hostname);
#endif
fclose(kfile);
curpgpkey[0] = TRUE;
}
unlink(f);
}
break;
#endif
}
} else {
/* Check for conflicting options. */
if (rtp && vat) {
fprintf(stderr, "Cannot choose both -RTP and -VAT protocols simultaneously.\n");
return 2;
}
if (rtp || vat) {
#ifdef CRYPTO
if (curideakey[0] || curpgpkey[0] ||
curblowfish_spec || curotp != NULL) {
fprintf(stderr, "Blowfish, IDEA, PGP, and Key File encryption cannot be used\n with -RTP or -VAT protocols.\n");
return 2;
}
#endif
if (compressing) {
fprintf(stderr, "Simple (-C) compression cannot be used with\n -RTP or -VAT protocols.\n");
return 2;
}
if (lpc10compress) {
fprintf(stderr, "LPC-10 (-LPC10) compression cannot be used with\n -RTP or -VAT protocols.\n");
return 2;
}
if (ring) {
fprintf(stderr, "Warning: ring not implemented in -RTP and -VAT protocols.\n");
}
}
if (dests == NULL) {
if (!addest(op)) {
return 1;
}
} else {
int ok = sendfile(op);
if (ok != 0)
return ok;
sentfile++;
}
}
}
if (dests == NULL) {
usage();
} else {
if (sentfile == 0) {
return sendfile(NULL);
}
}
exiting();
gsm_destroy(gsmh);
lpc_end();
#ifdef CRYPTO
desdone();
#endif
return 0;
}